package com.seckill.service.impl;

import com.seckill.dao.SeckillDao;
import com.seckill.dao.SuccessKilledDao;
import com.seckill.dao.cache.RedisDao;
import com.seckill.dto.Exposer;
import com.seckill.dto.SeckillExecution;
import com.seckill.entity.Seckill;
import com.seckill.entity.SuccessKilled;
import com.seckill.enums.SeckillStateEnum;
import com.seckill.exception.RepeatKillException;
import com.seckill.exception.SeckillCloseException;
import com.seckill.exception.SeckillException;
import com.seckill.service.SeckillService;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Desc 秒杀业务实现类
 * Auth Shinelon
 * Date 2017/7/5
 */
@Service("seckillServiceImpl")
public class SeckillServiceImpl implements SeckillService {

    private static Logger logger = Logger.getLogger(SeckillServiceImpl.class);

    //MD5盐值字符串
    private final String slat = "adfsfasdf*07*&&)8f12ja;lfjasdfu07";

    @Resource
    private RedisDao redisDao;

    @Resource
    private SeckillDao seckillDao;

    @Resource
    private SuccessKilledDao successKilledDao;

    public List<Seckill> getSeckillList() {
        return seckillDao.queryAll(0, 4);
    }

    public Seckill getById(Long seckillId) {
        return seckillDao.queryById(seckillId);
    }

    /**
     * 暴露秒杀接口
     * @param seckillId
     * @return
     */
    public Exposer exportSeckillUri(Long seckillId) {

        //1 访问redis
        Seckill seckill = null;
        seckill = redisDao.getSeckill(seckillId);
        if(seckill == null){
            seckill = seckillDao.queryById(seckillId);
            if (seckill == null) {
                return new Exposer(false, seckillId);
            }else{
                redisDao.putSeckill(seckill);
            }
        }

        Date startTime = seckill.getStartTime();
        Date nowTime = new Date();
        if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > seckill.getEndTime().getTime()) {
            return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), seckill.getEndTime().getTime());
        }
        String md5 = getMd5(seckillId);
        return new Exposer(true, md5, seckillId);
    }

    /**
     * 执行秒杀操作
     * @param seckillId
     * @param userPhone
     * @param md5
     * @return
     * @throws SeckillException
     * @throws SeckillCloseException
     * @throws RepeatKillException
     */
    @Transactional
    /**
     * 使用注解控制事物的优点
     * 1 明确事物方法的编程风格
     * 2 保证事物方法的执行时间尽可能短,不能穿插其他网络操作,RPC/HTTP/操作,如果需要这些操作则需要剥离到事物方法外
     * 3 不是所有的方法都需要事物,只有一条操作修改的时候(innodb 数据库引擎支持行级锁)或者只读操作,都是不需要事物控制
     */
    public SeckillExecution executeSeckill(Long seckillId, Long userPhone, String md5) throws SeckillException, SeckillCloseException, RepeatKillException {
        if (md5 == null || !md5.equals(getMd5(seckillId))) {
            throw new SeckillException("seckill data rewrite");
        }
        try {
            // 秒杀逻辑 减库存,更新秒杀成功表
            Date nowTime = new Date();

            //秒杀陈功,记录购买行为
            int insertNum = successKilledDao.insertSuccessKilled(seckillId, userPhone);

            if (insertNum <= 0) {
                //没有秒杀成功
                throw new RepeatKillException("seckill is repeat");
            } else {
                //秒杀陈功,记录购买行为
                int updateNum = seckillDao.reduceNumber(seckillId, nowTime);
                // 如果重复购买,则不能记录购买行为
                if (updateNum <= 0) {
                    throw new SeckillCloseException("seckill closed");
                } else {
                    SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
                    return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled);
                }
            }
        }catch (SeckillCloseException e){
            throw  e;
        }catch (RepeatKillException e){
            throw e;
        }catch (Exception e){
            logger.error(e.getMessage(),e);
            throw new SeckillException("seckill inner error: " + e.getMessage());
        }
    }

    /**
     * 使用密钥和seckillID 生成MD5串
     * @param seckillId
     * @return
     */
    private String getMd5(Long seckillId) {
        String base = slat + "/" + seckillId;
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }

    /**
     * MYSQL 存储过程实现的秒杀逻辑操作
     * @param seckillId
     * @param userPhone
     * @param md5
     * @return
     * @throws SeckillException
     * @throws SeckillCloseException
     * @throws RepeatKillException
     */
    public SeckillExecution executeSeckillProcedure(Long seckillId, Long userPhone, String md5) throws SeckillException, SeckillCloseException, RepeatKillException {
        if (md5 == null || !md5.equals(getMd5(seckillId))) {
            throw new SeckillException("seckill data rewrite");
        }

        Date killTime = new Date();
        Map map = new HashMap<String,Object>();
        map.put("seckillId",seckillId);
        map.put("phone",userPhone);
        map.put("killTime",killTime);
        map.put("result",null);
        try{
            seckillDao.killByProcedure(map);
            //获取result;
            int result = MapUtils.getInteger(map,"result",-2);
            if(result == 1){
                SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId,userPhone);
                return new SeckillExecution(seckillId,SeckillStateEnum.SUCCESS,successKilled);
            }else{
                return new SeckillExecution(seckillId,SeckillStateEnum.stateOf(result));
            }
        }catch(Exception e){
            logger.error(e.getMessage(),e);
            return new SeckillExecution(seckillId,SeckillStateEnum.INNER_KILL);
        }
    }
}
